package com.itheima.visitor;

import org.junit.Assert;
import org.objectweb.asm.*;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class VisitorChecker {

    private final String className;
    private final String methodName;
    ClassReader cr;

    public VisitorChecker(String className, String methodName) {
        this.className = className;
        this.methodName = methodName;
        try {
//            InputStream classBytecode = getClass().getResourceAsStream(className + ".class");
            // get bytecode of class names className
            InputStream classBytecode = getClass().getClassLoader().getResourceAsStream(className + ".class");

            if (classBytecode == null) {
                Assert.fail("类" + className + "不存在");
            }
            cr = new ClassReader(classBytecode);
        } catch (IOException e) {
            Assert.fail(String.format("类%s不存在", className));
        }
    }

    public void checkVariable(String variableType, boolean isNeedExists) {
        assertTarget(isNeedExists, String.format("声明%s变量", variableType), new CommonClassVisitor(methodName) {

            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                if (!Objects.equals(methodName, name)) {
                    return null;
                }
                return new MethodVisitor(Opcodes.ASM9) {
                    @Override
                    public void visitLocalVariable(String name1, String descriptor1, String signature1, Label start, Label end, int index) {
                        Type type = Type.getType(descriptor1);
                        if (type.getClassName().equals(variableType)) {
                            setHasTarget(true);
                        }
                    }
                };
            }
        });

    }

    public void checkField(String objectName, boolean isNeedExists) {

        assertTarget(isNeedExists, String.format("创建%s对象", objectName), new CommonClassVisitor(methodName) {

            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                if (!Objects.equals(methodName, name)) {
                    return null;
                }
                return new MethodVisitor(Opcodes.ASM9) {
                    @Override
                    public void visitTypeInsn(int opcode, String type) {
                        if (opcode != Opcodes.NEW) {
                            return;
                        }
                        System.out.println(type + " -> " + objectName);
                        if (Objects.equals(type, objectName) ||
                                Objects.equals(type, objectName.replace(".", "/"))) {
                            setHasTarget(true);
                        }
                    }
                };
            }
        });
    }

    static Map<String, List<Integer>> opcodeMap = new HashMap<>();

    static {
        opcodeMap.put("for", List.of(Opcodes.GOTO, Opcodes.IFEQ, Opcodes.IFLT));
    }

    public void checkJumpInsn(String statement, boolean isNeedExists) {
        if (!opcodeMap.containsKey(statement)) {
            return;
        }

        List<Integer> opcodeList = opcodeMap.get(statement);

        assertTarget(isNeedExists, String.format("包含%s循环", statement), new CommonClassVisitor(methodName) {

            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                if (!Objects.equals(methodName, name)) {
                    return null;
                }
                Set<Integer> targetCodes = new HashSet<>();
                return new MethodVisitor(Opcodes.ASM9) {
                    @Override
                    public void visitJumpInsn(int opcode, Label label) {
                        targetCodes.add(opcode);
                        if (targetCodes.containsAll(opcodeList)) {
                            setHasTarget(true);
                        }
                    }
                };
            }
        });
    }

    public void checkInvoke(String methodOwner, String methodName, boolean isNeedExists) {
        assertTarget(isNeedExists, String.format("调用%s的%s方法", methodOwner, methodName),
                new CommonClassVisitor(methodName) {

                    @Override
                    public MethodVisitor visitMethod(int access, String name, String
                            descriptor, String signature, String[] exceptions) {
                        if (!Objects.equals(VisitorChecker.this.methodName, name)) {
                            return null;
                        }
                        final List<Integer> opcodes = List.of(
                                Opcodes.INVOKEVIRTUAL, Opcodes.INVOKESTATIC,
                                Opcodes.INVOKESPECIAL, Opcodes.INVOKEINTERFACE);

                        return new MethodVisitor(Opcodes.ASM9) {
                            @Override
                            public void visitMethodInsn(int opcode, String owner,
                                                        String name, String descriptor,
                                                        boolean isInterface) {

                                if (!opcodes.contains(opcode)) {
                                    return;
                                }
                                // System.out.println(owner + ": " + name + " -> " + descriptor);
                                if (Objects.equals(methodOwner, owner) || Objects.equals(methodOwner.replace(".", "/"), owner)) {
                                    if (Objects.equals(methodName, name)) {
                                        setHasTarget(true);
                                    }
                                }
                            }
                        };
                    }
                });
    }

    public void check(String messageFormat, CommonMethodVisitor visitor) {
        final CommonClassVisitor mf = new CommonClassVisitor(methodName, visitor);
        assertTarget(isNeeded, messageFormat, mf);
    }

    private void assertTarget(boolean isNeedExists, String messageFormat, CommonClassVisitor mf) {
        cr.accept(mf, 0);

        String positive = String.format("%s类的%s方法中需要" + messageFormat, className, methodName);
        String negative = String.format("%s类的%s方法中不能" + messageFormat, className, methodName);
        if (isNeedExists) {
            Assert.assertTrue(positive, mf.achieveTarget());
        } else {
            Assert.assertFalse(negative, mf.achieveTarget());
        }
    }

    String msgFormat = "目标";
    boolean isNeeded = true;

    public static VisitorChecker checkMethod(String className, String methodName) {
        return new VisitorChecker(className, methodName);
    }
    public static VisitorChecker checkMethod(Class<?> clazz, String methodName) {
        return new VisitorChecker(clazz.getName(), methodName);
    }

    public VisitorChecker messageFormat(String msgFormat) {
        this.msgFormat = msgFormat;
        return this;
    }

    public VisitorChecker setPositive(boolean isNeeded) {
        this.isNeeded = isNeeded;
        return this;
    }

    public void check(CommonMethodVisitor visitor) {
        this.check(msgFormat, visitor);
    }
}